#region License
//-----------------------------------------------------------------------------
// Copyright (c) 2008, Aaron MacDougall, Daniel Jeffery
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of Aaron MacDougall or Daniel Jeffery nor the names of its contributors may
//   be used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace Agro2D
{
    public class ParseHelper
    {
        /// <summary>
        /// Test a converted value, output useful message if value is null.
        /// </summary>
        /// <param name="value">Value being tested</param>
        /// <param name="type">The type that is being converted</param>
        /// <param name="prop">The property that the value is being added to</param>
        static public void TestConvertedValue(object value, Type type, PropertyInfo prop, object unConvertedValue)
        {
            string typeMessage = "";
            // Provide some useful feedback
            switch(prop.Name)
            {
                case "Texture":
                    typeMessage = "!! Can't find specified texture. Is the path and filename correct? "
                    + "- and has it been added to the project? !!";
                    break;
            }

            string message = "Conversion could not be performed on property \""
                + type.Name + "." + prop.Name + ".\n Unconverted value is: " + unConvertedValue.ToString()
                + "\n\n" + typeMessage + "\n";

            Debug.Assert(value != null, message);
            if(value == null)
                Log.Instance.LogMessage(message);
        }

        static public void PropertyError(Type type, string prop)
        {
            Log.Instance.LogMessage("Property: " + "'" + prop + "'" + " does not exist in: " + "'" + type.ToString() + "'"
                + ". Check your case and spelling.");
        }

        /// <summary>
        /// Parses a property value into the correct type of object.
        /// </summary>
        /// <param name="property">Property used to obtain type required.</param>
        /// <param name="value">String value to parse.</param>
        /// <returns></returns>
        static public object ParseValue(PropertyInfo prop, string value)
        {
            object result = null;

            // If the property is already a string, then use it straight away
            if(prop.PropertyType == typeof(string))
            {
                result = value;
            }
            else if(prop.PropertyType == typeof(Rectangle) ||
                prop.PropertyType == typeof(Color)
                )
            {
                string[] split;
                char[] separator = { ',' };
                int[] values = new int[4];

                // Split numbers into individual strings
                split = value.Split(separator);

                Debug.Assert(split.Length == 4);

                // Convert to integers
                for(int j = 0; j < 4; j++)
                    values[j] = int.Parse(split[j]);

                // Set value to the correct type
                if(prop.PropertyType == typeof(Rectangle))
                    result = new Rectangle(values[0], values[1], values[2], values[3]);
                else
                    result = new Color((byte)values[0], (byte)values[1], (byte)values[2], (byte)values[3]);
            }
            else if(prop.PropertyType == typeof(Point))
            {
                string[] split;
                char[] separator = { ',' };
                int x;
                int y;

                // Split numbers into individual strings
                split = value.Split(separator);

                Debug.Assert(split.Length == 2);

                // Convert to integers
                x = int.Parse(split[0]);
                y = int.Parse(split[1]);

                result = new Point(x, y);
            }
            else if(prop.PropertyType == typeof(Vector2))
            {
                result = ParseVector(value);
            }
            else if(prop.PropertyType == typeof(Single))
            {
                result = Convert.ChangeType(value, prop.PropertyType, null);
                if(result != null && prop.Name == "Rotation")
                    result = MathHelper.ToRadians((float)result);
            }
            
            else if(prop.Name == "Texture")
            {
                result = ParseSpecialValue(prop.Name, value);
            }
            else if(prop.Name == "SpriteEffect")
            {
                result = ParseSpecialValue(prop.Name, value);
            }
            else if(prop.Name == "CollisionType")
            {
                result = ParseSpecialValue(prop.Name, value);
            }
            else if(prop.Name == "PlayerIndex")
            {
                result = ParseSpecialValue(prop.Name, value);
            }
            else if(prop.Name == "CollisionGroups")
            {
                result = ParseSpecialValue(prop.Name, value);
            }
            else if(prop.Name == "CollidesWith")
            {
                result = ParseSpecialValue(prop.Name, value);
            }
            else
            {
                if(result == null)
                {
                    try
                    {
                        // Try to use a standard conversion
                        result = Convert.ChangeType(value, prop.PropertyType, null);
                    }
                    catch(Exception)
                    {
                        result = null;
                    }

                    if(result == null)
                    {
                        try
                        {
                            // Try Agro value
                            result = ParseSpecialValue(prop.Name, value);
                        }
                        catch
                        {
                            result = null;
                        }
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Parse Agro2D types
        /// </summary>
        /// <param name="name">Name of C# property that is being set</param>
        /// <param name="value">The string value of the data being converted from</param>
        /// <returns>Converted object</returns>
        static public object ParseSpecialValue(string name, string value)
        {
            object result = null;
            try
            {
                switch(name)
                {
                    case "Texture":
                        if(TextureManager.Instance.InLevelEditor)
                        {
                           // Need graphics device
                           return TextureManager.Instance.CreateEditorTexture(SpriteLoader.GraphicsDevice, value);
                        }
                        else
                            return TextureManager.Instance.CreateTexture(value);
                    case "SpriteEffect":
                        return (SpriteEffects)Enum.Parse(typeof(SpriteEffects), value, true);
                    case "CollisionType":
                        return (CollisionType)Enum.Parse(typeof(CollisionType), value, true);
                    case "PlayerIndex":
                        return (PlayerIndex)Enum.Parse(typeof(PlayerIndex), value, true);
                    case "CollisionGroups":
                        return ParseGroup(value);
                    case "CollidesWith":
                        return ParseGroup(value);
                }
            }
            catch
            {
                result = null;
            }

            //throw new FormatException("Cannot convert value:" + value + " of type: " + name);

            return result;
        }

        private static object ParseVector(string value)
        {
            string[] split;
            char[] separator = { ',' };
            float x;
            float y;

            // Split numbers into individual strings
            split = value.Split(separator);

            Debug.Assert(split.Length == 2);

            // Convert to floats
            x = float.Parse(split[0]);
            y = float.Parse(split[1]);

            return new Vector2(x, y);
        }

        /// <summary>
        /// Parses Collision groups. Either single or multiple groups in a string.
        /// </summary>
        /// <param name="value">String containing groups</param>
        /// <returns>Returns a collision group</returns>
        static public CollisionGroups ParseGroup(string value)
        {
            // if user has used GroupX instead of name.
            bool isDirectValue = false; 
            CollisionGroups group = CollisionGroups.None;

            if(value.Contains("0") || value.Contains("1") || value.Contains("2") || value.Contains("4") || value.Contains("6") || value.Contains("8") || 
                value.Contains("1") || value.Contains("1") || value.Contains("1") || value.Contains("1") || value.Contains("1"))
                isDirectValue = true;
            //if(int.Parse(value) == null)
                

            if(isDirectValue) {
                try
                {
                    group = (CollisionGroups)Enum.Parse(typeof(CollisionGroups), value, true);
                    isDirectValue = true;
                }
                catch
                {
                    isDirectValue = false;
                }
            }


            if(!isDirectValue)
            {
                string[] split;
                char[] separator = { ',' };

                // Split numbers into individual strings
                split = value.Split(separator);


                string[] collGroups = (string[])split;
                foreach(string coll in collGroups)
                {                    
                    // Remove spaces.
                    string collG = coll.TrimStart(new char[] {' '});

                    if(collG != "None")
                        group |= CollisionManager.Instance.GetCollisionGroupID(collG);
                }
            }

            return group;
        }
    }

    /// <summary>
    /// Eliminate expired items
    /// http://blogs.msdn.com/kevin_ransom/archive/2005/03/14/395101.aspx
    /// ie. this will result in an error:
    /// foreach (Item s in collection)
    /// {
    ///      if (s.expired == true)
    ///         collection.Remove(s);
    /// }
    /// RemoveItems<Sprite>.Execute(objects, delegate(Sprite item) { return item.IsToBeDeleted == true; }); 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public static class RemoveItems<T>
    {
        public delegate bool Decide(T item);

        public static void Execute(ICollection<T> collection, Decide decide)
        {
            List<T> removed = new List<T>();

            foreach(T item in collection)
            {
                if(decide(item))
                    removed.Add(item);
            }

            foreach(T item in removed)
            {
                collection.Remove(item);
            }

            removed.Clear();
        }

    }
}
